package app.view.component
{
	import flare.display.DirtySprite;
	import flare.display.TextSprite;
	import flare.query.methods.eq;
	import flare.vis.Visualization;
	import flare.vis.controls.HoverControl;
	import flare.vis.data.Data;
	import flare.vis.data.DataSprite;
	import flare.vis.data.EdgeSprite;
	import flare.vis.data.NodeSprite;
	import flare.vis.data.render.ArrowType;
	import flare.vis.events.SelectionEvent;
	import flare.vis.operator.label.RadialLabeler;
	import flare.vis.operator.layout.BundledEdgeRouter;
	import flare.vis.operator.layout.CircleLayout;
	
	import flash.display.Sprite;
	import flash.events.KeyboardEvent;
	import flash.geom.Rectangle;
	import flash.text.TextFormat;
	import flash.ui.Keyboard;
	import flash.utils.Dictionary;
	
	
	public class FSMVisualisationSprite extends Sprite
	{
		/** We will be rotating text, so we embed the font. */
		[Embed(source="fonts/verdana.ttf", fontName="Verdana")]
		private static var _font:Class;
		[Embed(source="fonts/verdanab.ttf", fontName="Verdana", fontWeight="bold")]
		private static var _fontB:Class;
			
		private var _vis:Visualization;
		
		private var inEdgeColour:uint = 0xff009900;
		private var outEdgeColour:uint = 0xffff0000;
		
		private var rollOverNodeColour:uint = 0xff0000cc;
		private var defautEdgeNodeColour:uint = 0xff0055cc;
		
		private var _isActive:Boolean;
		private var _bounds:Rectangle;
		private var _fmt:TextFormat = new TextFormat("Verdana", 12);
		private var _focus:NodeSprite;
		
		public function FSMVisualisationSprite():void
		{
			
  		}
  		
  		public function flush():void{
  			_isActive = false;
  			if(_vis && contains(_vis)){
  				removeChild(_vis);
  				_vis = null;
  			}
  				
  		}
  		
  		public function set dataProvider( d:Data ):void{
  			_isActive = true;
  			if(_vis && contains(_vis))
  				removeChild(_vis);
  			visualize( d );
  		}
  		
  		public function update():void{
  			_vis.update();
  			
  		}
  		
  		public function get isActive():Boolean{
  			return _isActive;
  		}
  		
  		private function visualize(data:Data):void
		{
			// place shorter names at the end of the data list
			// that way they will the easiest to mouse over later
			data.nodes.sortBy("-data.name.length");
			//data.nodes.sortBy("data.order");
			
			// prepare data with default settings
			data.nodes.setProperties({
				shape: null,                  // no shape, use labels instead
				visible: eq("childDegree",0), // only show leaf nodes
				buttonMode: true              // show hand cursor
			});
			data.edges.setProperties({
				lineWidth: 0.25,
				lineColor: 0xff0055cc,
				arrowHeight:8,
				arrowWidth: 6,
				alpha:0.08,
				mouseEnabled: false ,          // non-interactive edges
				visible: true
			});
						
			// define the visualization
			_vis = new Visualization(data);
			// place around circle by tree structure, radius mapped to depth
			// make a large inner radius so labels are closer to circumference
			
			_vis.operators.add(new CircleLayout("depth", null, true));
			CircleLayout(_vis.operators.last).startRadiusFraction = .25;
			
			// bundle edges to route along the tree structure
			_vis.operators.add(new BundledEdgeRouter(.975));
			
			// add labels	
			_vis.operators.add(new RadialLabeler("data.name", true, _fmt, eq("childDegree",0))); // leaf nodes only
			_vis.operators.last.textMode = TextSprite.EMBED; // embed fonts!
			
			// update and add
			_vis.update();
			addChild(_vis);
			
			
			
			
			
			// add mouse-over highlight
			var hov:HoverControl = new HoverControl(NodeSprite,
				HoverControl.DONT_MOVE, rollover, rollout);
			_vis.controls.add(hov);
			
			// compute the layout
			if (_bounds) resize(_bounds);
		}
		
		/** Add highlight to a node and connected edges/nodes */
		private function rollover(evt:SelectionEvent):void
		{
			
			// highlight mouse-over node
			evt.node.props.label.color = 0xcccccc;
			evt.node.props.label.bold = true;
			// highlight links for classes that depend on the focus in green
			
			if(evt.ctrlKey){
				hiLightEdges( evt.node, NodeSprite.IN_LINKS, inEdgeColour );
			}else{
				hiLightEdges( evt.node, NodeSprite.OUT_LINKS, outEdgeColour );
			}
			
			_focus= evt.node;
			
			stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
			stage.addEventListener(KeyboardEvent.KEY_UP, onKeyUp);

			dispatchEvent(evt);
				
			
		}
		
		private function rollout(evt:SelectionEvent):void{
			unhighlight(evt);
			stage.removeEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
			stage.removeEventListener(KeyboardEvent.KEY_UP, onKeyUp);
			_focus = null;
			dispatchEvent(evt);
		}
		
		
		private function onKeyDown(event:KeyboardEvent):void{
			 if (event.keyCode == Keyboard.CONTROL) 
				hiLightEdges( _focus, NodeSprite.IN_LINKS, inEdgeColour );
		}
		
		private function onKeyUp(event:KeyboardEvent):void{
			if (event.keyCode == Keyboard.CONTROL) 
				hiLightEdges( _focus, NodeSprite.OUT_LINKS, outEdgeColour );
		}
		
		private function hiLightEdges( node:NodeSprite, edgetype:uint, colour:uint ):void{
			
			// apply slection properties
			node.visitEdges(function(e:EdgeSprite):void {
				e.alpha = 0.8;
				e.lineColor = colour;
				if(edgetype == NodeSprite.IN_LINKS)e.source.props.label.color = colour;
				if(edgetype == NodeSprite.OUT_LINKS)e.target.props.label.color = colour;
				e.lineWidth = 1;
				e.arrowType = ArrowType.TRIANGLE,
				_vis.marks.setChildIndex(e, _vis.marks.numChildren-1);
			}, edgetype);
			
			unhighlight( node, (edgetype == NodeSprite.IN_LINKS)?NodeSprite.OUT_LINKS:NodeSprite.IN_LINKS )
		}
		
		
		
		/** Remove highlight from a node and connected edges/nodes */
		private function unhighlight(n:*, edgetype:uint=3):void
		{
			var node:NodeSprite = n is NodeSprite ?
				NodeSprite(n) : SelectionEvent(n).node;
			
			
			// set everything back to normal
			node.props.label.color = 0;
			node.props.label.bold = false;
			node.setEdgeProperties({
				alpha: .08,
				lineColor: defautEdgeNodeColour,
				lineWidth:0.25,
				arrowType:ArrowType.NONE,
				"source.props.label.color": 0,
				"target.props.label.color": 0
			}, edgetype);
		}
		
		/** Traverse all dependencies for a given class */
		private function showAllDeps(evt:SelectionEvent, linkType:int):void
		{
			// first, do a breadth-first-search to compute closure
			var q:Array = evt.items.slice();
			var map:Dictionary = new Dictionary();
			while (q.length > 0) {
				var u:NodeSprite = q.shift();
				map[u] = true;
				u.visitNodes(function(v:NodeSprite):void {
					if (!map[v]) q.push(v);
				}, linkType);
			}
			// now highlight nodes and edges in the closure
			_vis.data.edges.visit(function(e:EdgeSprite):void {
				e.visible = map[e.source] && map[e.target];
			});
			_vis.data.nodes.visit(function(n:NodeSprite):void {
				n.alpha = map[n] ? 1 : 0.4;
			});
		}
		
		
		
		public function resize(bounds:Rectangle):void
		{
			_bounds = bounds;
			
			if (_vis) {
				// automatically size labels based on bounds
				var d:Number = Math.min(_bounds.width, _bounds.height);
				_vis.data.nodes.setProperty("props.label.size",
					(d <= 650 ? 10 : d <= 725 ? 11 : 12),
					null, eq("childDegree",0));
				
				// compute the visualization bounds
				_vis.bounds.x = _bounds.x;
				_vis.bounds.y = _bounds.y //+ (0.06 * _bounds.height);
				_vis.bounds.width = _bounds.width;
				_vis.bounds.height = _bounds.height //- (0.05 * _bounds.height);
				// update
				_vis.update();
				
				// layout legend and details
				
			
				// forcibly render to eliminate partial update bug, as
				// the standard RENDER event routing can get delayed.
				// remove this line for faster but unsynchronized resizes
				DirtySprite.renderDirty();
			}
		}
		
		// --------------------------------------------------------------------
		
	
		
	} // end of class DependencyGraph
}